iT邦幫忙

2024 iThome 鐵人賽

DAY 28
1
DevOps

全端監控技術筆記---從Sentry到Opentelemetry系列 第 28

Day28--服務端/客戶端渲染與opentelememtry--以NextJS為例

  • 分享至 

  • xImage
  •  

前言

在上一篇中,我們使用了純客戶端渲染(CSR)的方式來討論前端與 Opentelemetry 的關係。本篇文章中我們將進一步討論如何在 Next.js 這樣的全端框架中,實現 服務端渲染(SSR)客戶端渲染(CSR) 的可觀測性,來涵蓋服務端和瀏覽器端的追蹤。

在NextJS中分辨 run time

按照之前我們的demo,如果是後端服務的話,我們必須要在入口文件中先對 opentelemetry node sdk 進行初始化,讓它能夠先執行所宣告的 instrumentation、覆蓋目標方法,才能進行追蹤。

那麼在 NextJS 中,要在哪裡宣告?在目前版本中,可以以下步驟:

  • next.config.js中開啟 instrumentationHook 設定:
const nextConfig = {
    experimental: {
        instrumentationHook: true,
    },
};
  • 在跟/app 同級的目錄下(App Router),新增 instrumentation.jsinstrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    // nodejs runtime logic
    console.log("---instrumentation in node runtime")
  }else{
    console.log("---instrumentation in other runtime")
  }
}

運行後,可以看到在編譯頁面之前,console 輸出我們定義的文字:

image

代表這一段script可以執行在 node run time、而且是 server render 之前。

接入 Opentelemetry sdk

從之前的demo我們已經知道,server side 和 client side(browser)需要不同的 instrumentation,所以在全端框架如 NextJS 中,需要分別設定和分別運行。

Server Side

新增一個 instrument.node.js,把之前 demo 過的 NodeJS 中otel.js直接複製過來(不過要改成 ES Module 格式):

import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPTraceExporter } from 
...

export const initNodeOtel = () => {
    const resource = ...
    const logExporter =  ...
    const metricExporter = ...
    const traceExporter = ...
    const otelSdk = new NodeSDK({
        // SDK 設定
    });

    otelSdk.start();
};

然後在上一步創建的instrumentation.js/instrumentation.ts中,確認在 node runtime 的情況下再引入並呼叫,不然會報環境錯誤:

export async function register() {
    if (process.env.NEXT_RUNTIME === 'nodejs') {
        console.log('---instrumentation in node runtime');
        const { initNodeOtel } = await import('./lib/otel/instrument.node');
        initNodeOtel()
    }
}

同時可以注意,在 getNodeAutoInstrumentations 中最好另外設定 @opentelemetry/instrumentation-fs@opentelemetry/instrumentation-http,因為 NextJS 在路由呼叫的時候已經有許多框架內的邏輯(如fs、靜態資源獲取等等),會讓 trace 記錄擴增太多:

image
進入首頁後的服務端渲染 trace 記錄

client side 監控

針對客戶端監控,我們可以基於之前在 React 中的demo,修改其中的 otel.js、並重命名為instrument.browser.js

import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import {
    BatchSpanProcessor,
    SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-web';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
...


export const initBrowserOtel = () => {
    const resource = new Resource({ ... });

    const traceProvider = new WebTracerProvider({ resource });
    const traceExporter = new OTLPTraceExporter({
        url: exporterEndpoint.trace,
    });

    const spanProcessor = new SimpleSpanProcessor(traceExporter);
    traceProvider.addSpanProcessor(spanProcessor);

    // Register the provider
    traceProvider.register();

    // Auto-instrumentations
    registerInstrumentations({
        instrumentations: [
            getWebAutoInstrumentations(),
            new FetchInstrumentation({
                propagateTraceHeaderCorsUrls: /.*/,
            }),
        ],
        tracerProvider: traceProvider,
    });
};

接著我們要探討在何處運行這個 client side otel 的初始化。在之前的 React demo 中,我們是放在main.js之類的入口文件的最上層,進行全局攔截覆蓋;但在 NextJS 中,我們需要在 js 被傳到 browser 後的執行階段再運行,也就是客戶端渲染的情況下再運行 initBrowserOtel()

因此,我們可以直接新增一個 client render component,然後讓其被掛載後在進行初始化:

'use client';
import React from 'react';
import { initBrowserOtel } from '../lib/otel/instrument.browser';

export default function Otel() {
    React.useEffect(() => {
        initBrowserOtel();
    }, []);    
    return null;
}

然後可以透過 NextJS 的 Layout 機制(layout.js),把<Otel/>掛載到全局,也就是在 root layout 中使用:

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body
                className={`${geistSans.variable} ${geistMono.variable} antialiased`}
            >   
                <Otel />
                {children}
            </body>
        </html>
    );
}

結果測試

我們新增一個 api,然後在客戶端頁面呼叫該 api endpoint,測試一下測試 OpenTelemetry 的tracing:

  • API endpoint(app/api/demo/route.js
export async function GET(req, res) {
    await sleep(5000);
    const data = 'success';
    return Response.json({ data });
}
  • 客戶端頁面(app/demo/page.js
'use client';
import React from 'react';

export default function Demo() {
    const clickHandler = async () => {
        const res = await fetch('/api/demo', { method: 'GET' });
        const data = await res.json();
        console.log(data);
    };

    return (
        <div>
            <h1>Demo Page</h1>
            <button onClick={() => clickHandler()}>demo api</button>
        </div>
    );
}

  • 觸發後在Jaeger服務中:

image

可以看到 Jaeger 成功記錄從 NextJS client side 到 server side 的鏈路追蹤。

小結

這篇文章展示了如何在 NextJS 中實現全端的可觀測性。無論是 server-side 還是 client-side,我們都可以通過 OpenTelemetry 來進行監控和追蹤,幫助我們快速定位和解決問題。在服務端渲染(SSR)的架構中,我們介紹了如何基於 instrumentationHook 來實現 server-side 的 instrumentation;而在 client-side,我們通過通過 Web SDK 和 NextJS layout 機制來實現前端的 instrumentation。

本文完整程式碼可以在此 Github repository 中查看。

ref

ChangeLog

  • 20241012--修改內文
  • 20241002--初稿

上一篇
Day27--手寫在 Otel 中的前端 tracing data
下一篇
Day29--關於 Opentelemetry 中的 Batch Processor
系列文
全端監控技術筆記---從Sentry到Opentelemetry30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言